/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package systemsim;

import java.util.*;

/**
 * The Kitchen:
 * -receives requests for group food orders, and processes them.
 *  -keeps track of the individual food orders within a group order(for ex, a group order may contain several individual orders)
 *  -keeps track of the chefs and allocates individual food orders to available chefs.
 * @author emil
 */
public class Kitchen {
    
    private GUIKitchen guiKitchen;
    
    private Random rand = new Random();
        
    private int numOfChefs;
    private int platePrepTime;  //Random factor?? 
    
    private LinkedList<GroupFoodOrder> tobePreparedQueue = (new LinkedList<GroupFoodOrder>());
    private LinkedList<GroupFoodOrder> beingPreparedList = (new LinkedList<GroupFoodOrder>());
    private LinkedList<Group> readyToServeQueue = (new LinkedList<Group>());
    
    private ArrayList<FoodOrder> chefs = (new ArrayList<FoodOrder>(numOfChefs));   // each element in this list represents 1 chef, 1 chef can work on 1 plate at a time
    
    /*Background Daemon process which will handle food preparation
     * -receives requests for group food orders, and processes them.
     *  -keeps track of the individual food orders within a group order(for ex, a group order may contain several individual orders)
     *  -keeps track of the chefs and allocates individual food orders to available chefs.
     */
    public static Timer kitchenProcess = new Timer();
        
    
    public Kitchen(int numberOfChefs, int platePreparationTime)
    {
        this.numOfChefs = numberOfChefs;
        this.platePrepTime = platePreparationTime;
        guiKitchen = new GUIKitchen();
        //start the kitchen process, runs every 1/10th of a second
        kitchenProcess.schedule(new ProcessFoodOrdersTask(),0,100);
        
        
        
        
    }
        
    /**
     * adds a food order for the specified group
     * @param group the group
     */
    public void addOrder(Group group)
    {
        synchronized(Main.kitchenLock)
        {
            GroupFoodOrder gf = new GroupFoodOrder(group);
            
            boolean status = tobePreparedQueue.add(gf);
            if(status = false)
            {
                System.out.println("KITHCEN ERROR - fail to add order");
            }
            else
            {
                System.out.println("KITHCEN - Added new food order"+gf );
            }
        }
        
    }
    
    /**
     * @return returns a group whose food is ready, null if there are no plates which are ready.
     */
    public Group getPreparedFood()
    {
        synchronized(Main.kitchenLock)
        {
            if(!readyToServeQueue.isEmpty())
            {
                return this.readyToServeQueue.removeFirst();
            }
            else
            {
                return null;
            }
            
        }
    }

    public int getNumOfChefs() {
        return numOfChefs;
    }

    public void setNumOfChefs(int numOfChefs) {
        this.numOfChefs = numOfChefs;
    }

    public int getPlatePrepTime() {
        return platePrepTime;
    }

    public void setPlatePrepTime(int platePrepTime) {
        this.platePrepTime = platePrepTime;
    }
    
    
    
    
    
    private class GroupFoodOrder
    {
        Group group;
        int platesLeftToProcess;
        
        public GroupFoodOrder(Group gr)
        {
            this.group = gr;
            this.platesLeftToProcess = group.getNumPeople();
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final GroupFoodOrder other = (GroupFoodOrder) obj;
            if (this.group != other.group && (this.group == null || !this.group.equals(other.group))) {
                return false;
            }
            return true;
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 61 * hash + (this.group != null ? this.group.hashCode() : 0);
            return hash;
        }

        @Override
        public String toString() {
            return "GroupFoodOrder{" + "group=" + group.getID() + '}';
        }
        
        
        
    }
    
    
    
    
    
    public class FoodOrder
    {
        Group group;                //the group
        int platesLeftToProcess;    //the number of plates left to process in the group
        long timeOrderStarted;      //the time in milliseconds when the kithcen starts to work on this order
        long timeToPrepThisDish;     //the time it takes to make this plate 
        
        
        public FoodOrder(Group group, int platesLeftInGroup)
        {
            this.timeOrderStarted = System.currentTimeMillis();
            this.group = group;                                         //the group which this order is part of
            this.timeToPrepThisDish = (1000L*60L*(long)(this.generatePrepTime(platePrepTime)))/Main.TIMESCALE;  //generates random dish prep time based on the specified average prep time
            //System.out.println(this.timeToPrepThisDish);
            this.platesLeftToProcess = platesLeftInGroup;
            
            
        }
        
       
         //Method to get +- dish prep time
        private int generatePrepTime(int averageDishPrepTime)
        {

            int k = rand.nextInt(5);
            
            return averageDishPrepTime + k;
            
        }
        
       
    }
    
    
    
    
    
    
    private class ProcessFoodOrdersTask extends java.util.TimerTask
    {
        public void run()
        {
            //System.out.println("KithchenRun");
             long currTime = System.currentTimeMillis();
                                       
             
             //Go through Chefs, check if individual plates are ready, update group orders accordingly
             synchronized(Main.kitchenLock){
             
                Iterator<FoodOrder> chefsIter = chefs.iterator();
                while(chefsIter.hasNext())
                {
                    //System.out.println("KithchenRun2");
                    
                    
                    
                    FoodOrder fdor = chefsIter.next();
                 
                    //System.out.println((fdor.timeOrderStarted + fdor.timeToPrepThisDish) - currTime);
                    
                    //IF Food Order is ready
                    if ((fdor.timeOrderStarted + fdor.timeToPrepThisDish) <= currTime)
                    {
                        //System.out.println("KithchenRun2inside");
                        guiKitchen.remove(fdor);
                        chefsIter.remove();    // free the chef who is cooking this order
                        
                        
                        //IF the group order is ready
                        if(fdor.platesLeftToProcess < 1)
                        {
                            
                            readyToServeQueue.add(fdor.group);
                            System.out.println("KITCHEN - just finihsed order for group"+fdor.group);
                            
                   
                            if(!beingPreparedList.remove(new GroupFoodOrder(fdor.group)))
                            {
                                //throw new Exception("Error removing food order from beingPreparedList!");
                                System.out.println("Error removing food order from beingPreparedList!");
                            }
                            
                        }
                        
                    }
                }
                
                
              
                  
                  
                //IF there are available chefs, put in more dishes, check existing orders first
                if(chefs.size() < numOfChefs )
                {
                    //System.out.println("KithchenRun3");
                    for(int i = 0; i<10; i++)    //This should be max number of ppl in a group! (10 for now)
                    {
                    
                        //if there are pending group orders, continue their fullfilment
                        if(beingPreparedList.size() > 0)
                        {
                            GroupFoodOrder curr = beingPreparedList.getFirst();
                            if(curr.platesLeftToProcess > 0)
                            {
                                //chefs.add((numOfChefs-chefs.size()-1), new FoodOrder(curr.group, --curr.platesLeftToProcess));
                                FoodOrder fd = new FoodOrder(curr.group, --curr.platesLeftToProcess);
                                chefs.add(fd);
                                guiKitchen.add(fd);
                            }

                        }
                    }
                }
                    
                    //if there are no pending orders, check if there are orders waiting to be started
                    if(chefs.size() < numOfChefs)
                    {
//                        System.out.println(tobePreparedQueue.size());
//                        System.out.println(beingPreparedList.size());
//                        System.out.println(numOfChefs);
                        
                        if((tobePreparedQueue.size() > 0) && (beingPreparedList.size() < numOfChefs))
                        {
                            //if(curr !=null)
                            {
                                GroupFoodOrder curr = tobePreparedQueue.getFirst();
                            
                                tobePreparedQueue.remove(curr);
                                beingPreparedList.add(curr);
                                //chefs.add((numOfChefs-chefs.size()-1), new FoodOrder(curr.group, --curr.platesLeftToProcess));
                                FoodOrder fd = new FoodOrder(curr.group, --curr.platesLeftToProcess);
                                chefs.add(fd);
                                guiKitchen.add(fd);
                            }
                            
                        }
                    }

                 //break;   
                
                
             
                //System.out.println("KithchenRun4");

                
             }
            guiKitchen.setSizeDisplay(numOfChefs);
        }
    }
    
   
}
